package jobscheduling.algorithms.bee;

import java.util.ArrayList;
import java.util.Map;
import java.util.Random;
import jobscheduling.algorithms.ResultListener;
import jobscheduling.model.InputData;
import jobscheduling.model.SolutionEvaluator;

public class BeeColony {
	/**
	 * UWAGA!!! czesto zmienna solution jest typu int[], a nie instancja klasy
	 * Solution!!
	 **/

	Solution[] solutions_;
	private int[] trials_;
	private Random rand_;
	private InputData data_;
	private ResultListener listener_;
	/**
	 * 0 - jedna zamiana, 1 - jedno wstawienie, 2 - dwie zamiany, 3 - dwa
	 * wstawienia, 4 - przy każdej mutacji rozwiązania wybierana jest losowa
	 * opcja
	 **/
	private int mutateMode_ = 4;
	/** ilosc zrodel pozywienia == employedBees */
	private int foodSourcesNumber_ = 120;
	/* ilosc zrodel pozywienia, 2* foodSourcesNumber */
	private int onlookerBeesNumber_ = 240;
	/*
	 * ilosc usuwanych najgorszych rozwiazan z kazdym przejsciem petli,
	 * 0.2*foodSourcesNumber
	 */
	private int scoutBeesCount_ = 24;
	/* ilosc cykli eksploracyjnych - kryterium stopu */
	private int cycleCount_ = 1000;
	/* czy uzywac local search */
	private boolean ifLocalSearch_ = false;
	/*jak usuwac najgorsze - 0 lowest eval, 1-max trial*/
	private int scoutMode_ = 0;
	
	public BeeColony(int[][] jobList, ResultListener listener,
			Map<String, String> params) {
		cycleCount_ = Integer.parseInt(params.get("cycleCount"));
		foodSourcesNumber_ = Integer.parseInt(params.get("foodSourcesNumber"));
		onlookerBeesNumber_ = Integer
				.parseInt(params.get("onlookerBeesNumber"));
		scoutBeesCount_ = Integer.parseInt(params.get("scoutBeesCount"));
		mutateMode_ = Integer.parseInt(params.get("mutateMode"));
		int tmp = Integer.parseInt(params.get("ifLocalSearch"));
		if (tmp == 1) {
			ifLocalSearch_ = true;
		} else {
			ifLocalSearch_ = false;
		}
		scoutMode_ = Integer.parseInt(params.get("scoutMode"));;
		solutions_ = new Solution[foodSourcesNumber_];
		trials_ = new int[foodSourcesNumber_];
		rand_ = new Random();
		data_ = new InputData(jobList.length, jobList[0].length, jobList);

		listener_ = listener;
		for (int i = foodSourcesNumber_ - 1; i >= 0; i--) {
			init(i);
		}
	}

	/** 
	 * Każda EmployedBee szuka lepszych rozwiazan, na podstawie tych starych 
	 **/
	public void sendEmployedBees() {
		int i;
		for (i = 0; i < foodSourcesNumber_; i++) {
			findNeighborhoodSolution(i);
		}
	}

	/**
	 * im lepsze rozwiazanie tym wiecej OnlookeerBees je wybiera i bardziej
	 * ulepsza
	 **/
	public void sendOnlookeerBees() {
		int i = 0;
		/* dwie losowe solucje, z ktorych poprawiana bedzie lepsza */
		int sol1, sol2;

		for (i = 0; i < onlookerBeesNumber_; i++) {
			sol1 = rand_.nextInt(foodSourcesNumber_);
			sol2 = rand_.nextInt(foodSourcesNumber_);
			/* sol1 bedzie miec numer lepszego rozwiazania */
			if (solutions_[sol1].getEval() < solutions_[sol2].getEval()) {
				sol1 = sol2;
			}
			findNeighborhoodSolution(sol1);
		}
	}

	/** 
	 * kazda ScoutBees usuwa jedno najgorsze rozwazanie 
	 **/
	public void sendScoutBees() {
		int solToInit, i,best = 0;
		/*trzyma liste najgorszych znalezionych rozwiazan, aby mozna bylo znajdowac "prawie najgorsze" rozwiazania*/
		ArrayList<Integer> initedSolList = new ArrayList<Integer>();
		if(scoutMode_ != 0){
			for (int i1 = 1; i1 < foodSourcesNumber_; i1++) {
				if (solutions_[i1].getEval() < solutions_[best].getEval()) {
					best = i1;
				}
			}
		}
		for (int jj = 0; jj < scoutBeesCount_; jj++) {
			solToInit = 0;
			for (i = 1; i < foodSourcesNumber_; i++) {
				/*najgorszy eval*/
				if(scoutMode_ != 1){
					if (solutions_[i].getEval() > solutions_[solToInit].getEval()){
						if (initedSolList.contains(i) != true)
							solToInit = i;
					}
				}
				/*najdluzszy trial*/
				else{
					/*zeby nie wymazac najlepszego rozwiazania*/
					if (trials_[i] > trials_[solToInit]){
						if (i != best) {
							if (initedSolList.contains(i) != true)
								solToInit = i;
						}
					}
				}
			}
			initedSolList.add(solToInit);
			init(solToInit);
		}
	}

	/**
	 * metoda wykorzystywana w sendEmployedBees i sendOnlookeerBees. Na zadanej
	 * solucji dokonuje odpowiedniej mutacji rozwiazania, a nastepnie (jesli wyspecyfikowano) wykonuje local
	 * search. Dodatkowo zwieksza trials_ w wypadku nie znalezienie lepszego
	 * rozwiazania
	 **/
	private void findNeighborhoodSolution(int solutionNumber) {
		int[] tmpSolution, mutatedSolution, localSearchSolution, returnSolution;
		int mutatedEval;
		tmpSolution = solutions_[solutionNumber].getSolution();
		mutatedSolution = mutate(tmpSolution, mutateMode_);
		/*
		 * jesli nie uzyskalismy lepszego rozwiazania to jako "poprawione" i tak
		 * uznajemy to stare rozwiazanie
		 */
		mutatedEval = SolutionEvaluator.getEval(data_, mutatedSolution);
		if (solutions_[solutionNumber].getEval() < mutatedEval) {
			mutatedSolution = tmpSolution;
			mutatedEval = solutions_[solutionNumber].getEval();
			/* nie znaleziono lepszego rozwiazania, trial rosnie */
			trials_[solutionNumber]++;
		} else {
			trials_[solutionNumber] = 0;
		}
		returnSolution = mutatedSolution;
		if (ifLocalSearch_) {
			/* Local Search na lepszym rozwiazaniu */
			localSearchSolution = VNS(mutatedSolution);
			/* wybieramy lepsze rozwiazanie i zapisuejmy do tablicy rozwiazan */
			if (SolutionEvaluator.getEval(data_, localSearchSolution) <= mutatedEval) {
				returnSolution = localSearchSolution;
				trials_[solutionNumber] = 0;
			} else {
				trials_[solutionNumber]++;
			}
		}
		solutions_[solutionNumber] = new Solution(data_, returnSolution);
	}

	/**
	 * rozwiazanie solutions[number] zostaje skasowane i w jego miejsce powstaje
	 * nowe losowe rozwiazanie
	 */

	public void init(int number) {
		int jobs = data_.getJobs();
		int i, tmp;
		Random rand = new Random();
		ArrayList<Integer> list = new ArrayList<Integer>();
		for (i = 0; i < jobs; i++) {
			list.add(i);
		}
		int[] tmpSol = new int[jobs];
		// w kolejne miejsca wpisywane sa losowo wybrane prace
		for (i = 0; i < jobs; i++) {
			// po kazdej iteracji losujemy z coraz mniejszej puli
			tmp = rand.nextInt(jobs - i);
			tmpSol[i] = list.get(tmp);
			list.remove(tmp);
		}
		solutions_[number] = new Solution(data_, tmpSol, 0);
	}

	/**
	 * tworzy odpowiednia mutacje podanego rozwiazania w zaleznosci od podanego
	 * parametru
	 **/
	public int[] mutate(int[] solution, int mode) {
		int jobs = data_.getJobs();
		int[] mutated = null;
		int[] tmpSolution;
		switch (mode) {
		case 0:
			mutated = swap(solution, rand_.nextInt(jobs), rand_.nextInt(jobs));
			break;
		case 1:
			mutated = insert(solution, rand_.nextInt(jobs), rand_.nextInt(jobs));
			break;
		case 2:
			tmpSolution = swap(solution, rand_.nextInt(jobs),
					rand_.nextInt(jobs));
			mutated = swap(tmpSolution, rand_.nextInt(jobs),
					rand_.nextInt(jobs));
			break;
		case 3:
			tmpSolution = insert(solution, rand_.nextInt(jobs),
					rand_.nextInt(jobs));
			mutated = insert(tmpSolution, rand_.nextInt(jobs),
					rand_.nextInt(jobs));
			break;
		default:
			mutated = mutate(solution, rand_.nextInt(4));
			break;
		}
		return mutated;
	}

	/**
	 * Zamienia dwa wybrane elementy miejscami w podanym rozwiazaniu int[] -> w
	 * poszukiwaniu leszpego rozwiazania
	 **/
	public int[] swap(int[] solution, int pos1, int pos2) {
		int tmp;
		int[] swaped = solution.clone();
		tmp = swaped[pos1];
		swaped[pos1] = swaped[pos2];
		swaped[pos2] = tmp;
		return swaped;
	}

	/**
	 * wstawia wybraną prace w wybrane miejsce w podanym rozwiazaniu int[] -> w
	 * poszukiwaniu leszpego rozwiazania
	 **/
	public int[] insert(int[] solution, int fromGet, int wherePut) {
		int tmp;
		int[] inserted = solution.clone();
		
		if (fromGet == wherePut) {
			return inserted;
		}
		tmp = inserted[fromGet];
		if (fromGet < wherePut) {
			for (int i = fromGet; i < wherePut; i++) {
				inserted[i] = inserted[i + 1];
			}
		} else {
			for (int i = fromGet; i > wherePut; i--) {
				inserted[i] = inserted[i - 1];
			}
		}
		inserted[wherePut] = tmp;
		return inserted;
	}

	/**
	 * funkcja optymalizujaca rozwiazanie(Local Search), ktore juz przeszlo "mutacje" (mutate)
	 **/
	private int[] VNS(int[] solution) {
		boolean flag = false;
		int[] sol0 = solution.clone(), sol1, sol2;
		do {
			sol1 = insertLS(sol0);
			sol2 = sawpLS(sol1);
			if (SolutionEvaluator.getEval(data_, sol2) < SolutionEvaluator
					.getEval(data_, sol0)) {
				flag = true;
				sol0 = sol2;
			} else {
				flag = false;
			}

		} while (flag == true);
		return sol0;
	}

	/**
	 *  modyfikuje rozwiazanie - wykorzystywane do Local Search przez VNS
	 **/
	private int[] sawpLS(int[] solution) {
		int i, j, n = data_.getJobs();
		int[] sol0 = solution.clone(), sol1;
		i = 1;
		do {
			j = i + 1;
			do {
				sol1 = sol0.clone();
				swap(sol1, i - 1, j - 1);
				if (SolutionEvaluator.getEval(data_, sol1) < SolutionEvaluator
						.getEval(data_, sol0)) {
					sol0 = sol1;
					i = 0;
					j = i + 1;
				} else {
					j = j + 1;
				}
			} while (j < n);
			i = i + 1;
		} while (i < n);
		return sol0;
	}

	/**
	 * modyfikuje rozwiazanie - wykorzystywane do Local Search przez VNS 
	 **/
	private int[] insertLS(int[] solution) {
		int i, j, n = data_.getJobs();
		int[] sol0 = solution.clone(), sol1;
		i = 1;
		do {
			j = i + 1;
			do {
				sol1 = sol0.clone();
				insert(sol1, i - 1, j - 1);
				if (SolutionEvaluator.getEval(data_, sol1) < SolutionEvaluator
						.getEval(data_, sol0)) {
					sol0 = sol1;
					i = 0;
					j = i + 1;
				} else {
					j = j + 1;
				}
			} while (j < n);
			i = i + 1;
		} while (i < n);
		return sol0;

	}

	private Solution getBestSolution() {
		int best = 0;
		for (int i = 1; i < foodSourcesNumber_; i++) {
			if (solutions_[i].getEval() < solutions_[best].getEval()) {
				best = i;
			}
		}
		return solutions_[best];
	}

	public Solution optimize() {
		Solution best = getBestSolution();
		for (int i = 0; i < cycleCount_; i++) {
			sendEmployedBees();
			sendOnlookeerBees();
			sendScoutBees();
			best = getBestSolution();
			/** poinformowanie GUI o wyniku **/
			listener_.newResult(best.getSolution(), best.getEval());
		}
		listener_.newResult(best.getSolution(), best.getEval());
		System.out.println();
		return best;
	}

}